<?php

class GXCurl {

	const GET_HEADER = 'GET_HEADER';
	const NO_RETURN = 'NO_RETURN';
	const GET		= 'GET';
	const POST		= 'POST';
	const DELETE	= 'DELETE';
	const PUT		= 'PUT';

	// store the header
	private $header;

	// cache the curl connection
	private $curl;

	// cache results
	private $cache;

	// store result code
	private $code;

	/**
	 * __construct
	 * @return boolean
	 */
	public function __construct(){
		$this->setHeader();
		return true;
	}

	public function setHeader( $head = null ){
		if( $head ){
			$this->header = $head;
		}
		return true;
	}

	public function getHeader(){
		if( !$this->header ){
			$this->header = array(
				'Content-Type: application/x-www-form-urlencoded'
				);
		}
		return $this->header;
	}

	public function getCode(){
		return $this->code;
	}

	private function handshake(){
		if( !$this->curl ){
			$this->curl = curl_init();
		}
		return $this->curl;
	}

	/**
	 * fetch
	 *
	 * Performs an HTTP request for the provided url.
	 * @param string $url the full url, including protocol
	 * @param mixed $data GXDOM, name=>value array or string
	 * @param string $method GET or POST
	 * @return object GXDOM or false on failure
	 */
	public function fetch(){
		// grab the arguments and rejigger them
		$args = func_get_args();
		$argnames = array('url','data');
		$url = null;
		$data = null;
		$getheader = false;
		$getreturn = true;
		$methodget = true;
		$methodpost = false;
		$methodput = false;
		$methoddelete = false;
		foreach( $args as $a ){
			switch(true){
				case $a === GXCurl::GET_HEADER:
					$getheader = true;
					continue;
					break;
				case $a === GXCurl::NO_RETURN:
					$getreturn = false;
					continue;
					break;
				case $a === GXCurl::GET:
					$methodget = true;
					$methodpost = false;
					$methodput = false;
					$methoddelete = false;
					continue;
					break;
				case $a === GXCurl::POST:
					$methodget = false;
					$methodpost = true;
					$methodput = false;
					$methoddelete = false;
					continue;
					break;
				case $a === GXCurl::PUT:
					$methodget = false;
					$methodpost = false;
					$methodput = true;
					$methoddelete = false;
					continue;
					break;
				case $a === GXCurl::DELETE:
					$methodget = false;
					$methodpost = false;
					$methodput = false;
					$methoddelete = true;
					continue;
					break;
				// if we're here, the arg is one of the variables we need
				default:
					$name = array_shift($argnames);
					${$name} = $a;
			}
		}
		// sanity check
		if( !$url ){
			trigger_error( 'Missing url', E_USER_ERROR );
			return false;
		}

		// method name
		$methodname = null;
		switch(true){
			case $methodget:
				$methodname = 'GET';
				break;
			case $methodpost:
				$methodname = 'POST';
				break;
			case $methoddelete:
				$methodname = 'DELETE';
				break;
			default:
				$methodname = 'INVALID METHOD (' .$methodget .'|' .$methodpost .'|' .$methodput .'|' .$methoddelete .')';
		}
		trigger_error('GXCURL: ' .$methodname .' with URL ' .$url, E_USER_WARNING);

		// format data
		$payload = null;
		switch(true){
			case is_object($data):
				$payload = $data->exec->xml();
				break;
			case is_array($data):
				$payload = http_build_query($data);
				break;
			default:
				$payload = $data;
		}

		// instantiate curl
		$ch = $this->handshake();
		// set the options
		curl_setopt( $ch, CURLOPT_URL, 				$url );
		curl_setopt( $ch, CURLOPT_HEADER,			$getheader );
		switch(true){
			case $methodget:
				curl_setopt( $ch, CURLOPT_HTTPGET,		true );
				curl_setopt( $ch, CURLOPT_POST,			false );
				break;
			case $methodpost:
				curl_setopt( $ch, CURLOPT_POST, 		true );
				curl_setopt( $ch, CURLOPT_POSTFIELDS, 	$payload );
				break;
			case $methodput:
				curl_setopt( $ch, CURLOPT_CUSTOMREQUEST,	'PUT' );
				curl_setopt( $ch, CURLOPT_HTTPHEADER,	array('Content-Length: ' .strlen($payload)) );
				curl_setopt( $ch, CURLOPT_POSTFIELDS,	$payload );
				break;
			case $methoddelete:
				curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, 'DELETE' );
				// this option is necessary, or google considers the request malformed
				curl_setopt( $ch, CURLOPT_NOBODY,		true );
				break;
		}
		curl_setopt( $ch, CURLOPT_FAILONERROR,		false );
		curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 	$getreturn );
		curl_setopt( $ch, CURLOPT_HTTPHEADER,		$this->getHeader() );
		trigger_error('GXCURL: CURLOPT_HTTPHEADER = ' .print_r($this->getHeader(), true), E_USER_ERROR);
		// if we're making an SSL request, turn off major errors
		if( substr($url,0,5) == 'https' ){
			curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER,	false );
			curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST,	false );
		}
		// make the request
		$response = curl_exec($ch);
		// check for errors
		if( curl_errno($ch) ){
			trigger_error( 'curl err for ' .$this->url .': ' .curl_errno($ch) .'::' .curl_error($ch), E_USER_ERROR );
			curl_close($ch);
			return false;
		}

		// test for success
		$code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
		$this->code = $code;
		switch($code){
			case '201':
				trigger_error( 'fetch(' .$url .') request completed successfully [201]', E_USER_WARNING );
				break;
			case '200':
				trigger_error( 'fetch(' .$url .') successful [200]', E_USER_WARNING );
				break;
			case '400':
				trigger_error( 'fetch(' .$url .') failed [400], bad request [' .$response .']', E_USER_ERROR );
				return false;
				break;
			case '403':
				trigger_error( 'fetch(' .$url .') failed [403], forbidden [' .$response .']', E_USER_ERROR );
				return false;
				break;
			case '404':
				trigger_error( 'fetch(' .$url .') failed [404], url not found [' .$response .']', E_USER_ERROR );
				return false;
				break;
			case '501':
				trigger_error( 'fetch(' .$url .') failed [501], not implemented [' .$response .']', E_USER_ERROR );
				return false;
				break;
			default:
				trigger_error( 'fetch(' .$url .') resulted in unexpected response code [' .$code .']; just sending back response (' .$response .')', E_USER_WARNING );
		}

		// if we have no response but the code was successful, return true (DELETE requests do not receive a return)
		if(!$response && $code == '200'){
			$response = true;
		}
		return $response;
	}

}
?>
